package it.webrules.deploymanager.core.config;

import it.webrules.deploymanager.core.UsageHelper;
import it.webrules.deploymanager.core.st.scm.Type;
import it.webrules.xsd.dmc.config.v1.GeneralConfig;
import it.webrules.xsd.dmc.config.v1.StaticConfig;
import it.webrules.xsd.dmc.config.v1.WebappConfig;
import it.webrules.xsd.dmc.config.v1.WebappConfig.Build;
import it.webrules.xsd.dmc.config.v1.WebappConfig.Deploys;
import it.webrules.xsd.dmc.config.v1.WebappConfig.Repository;

import java.io.File;
import java.io.FileNotFoundException;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class ConfigurationService {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	private WebappConfig webappConfig;
	private StaticConfig staticConfig;

	@Option(name = "-dmc", aliases = "--config", usage = "Deploy manager configuration file. Required.", metaVar = "CONFIGFILE")
	private File configFile = null;

	@Option(name = "-c", aliases = "--command", usage = "Command to execute. Required. \n\nFor Dynamic parts:\n"
			+ "Deploy - Deploy an artifact on servers.\nListBuilds - List available build number from repository, or, "
			+ "if repository isn't configured, from build system.\n\n"
			+ "For Static parts:\nListSwitchableDirs - List available directories for scm checkout/switch.\n"
			+ "TagTrunkAndDeploy/Checkout/Update/Info/Switch/Cleanup/Revert/CreateTag/CreateBranch/DeleteBranch/Merge - Standard command passed to SCM.\n"
			+ "StatusFile/DiffContent - List the difference in file system and in file content respectively.")
	private Command command = null;

	@Option(name = "-b", aliases = "--build", usage = "Set build number to get from repository system, if repository "
			+ "isn't configured, from build system. If not used, the tool ask for a new release if build system are "
			+ "configured or get the last release from repository system. Used when -a is not specified.")
	private String buildNumber;

	@Option(name = "-a", aliases = "--artifact", usage = "Skip the iteration with builder system / repository and deploy this artifact.")
	private File artifactFile;

	@Option(name = "-t", aliases = "--type", usage = "Used when command is Checkout/Deploy/CreateTagOrBranch. Identify remote folder type.")
	private Type type;

	@Option(name = "-st", aliases = "--srctype", usage = "Used when command is CreateTagOrBranch. Identify remote source folder type.")
	private Type srctype;

	@Option(name = "-sn", aliases = "--srcname", usage = "Used when command is CreateTagOrBranch. Source name of branch or tag.")
	private String srcname;

	@Option(name = "-s", aliases = "--switchin", usage = "Used when command is Create Tag/Branch. Switch workind copy to new tag/brach.")
	private Boolean switchin;

	@Option(name = "-h", aliases = "--help", usage = "Show this help.")
	private Boolean help;

	@Option(name = "-fc", aliases = "--forcecheckout", usage = "Used when command is Checkout. If you do checkout in a non empty folder the process will stop if --forcecheckout is not specified.")
	private Boolean forcecheckout;

	@Option(name = "-n", aliases = "--name", usage = "Used when command is TagTrunkAndDeploy/Checkout. Name of branch or tag.")
	private String name;

	@Option(name = "-rv", aliases = "--release-version", usage = "Use this release version instead of get from build/repository system.")
	private String releaseVersion;

	@Option(name = "-dv", aliases = "--development-version", usage = "Use this development version instead of get from build/repository system.")
	private String developmentVersion;

	@Option(name = "-go", aliases = "--graph-out-file", usage = "Output of svg file for graph svn repository.")
	private File graphOutputFile;

	public void configWithArgument(String[] argv) throws ConfigurationException {

		CmdLineParser parser = new CmdLineParser(this);

		try {

			parser.parseArgument(argv);

			if (help != null && help) {
				UsageHelper usageHelper = new UsageHelper(parser);
				usageHelper.printUsage();
				return;
			}

			if (configFile == null)
				throw new ConfigurationException("Option \"-dmc (--config)\" is required");

			if (command == null)
				throw new ConfigurationException("Option \"-c (--command)\" is required");

			configWith(configFile);

		} catch (Exception e) {
			throw new ConfigurationException("Configuration is not valid: " + e.getMessage(), e);

		}

	}

	@SuppressWarnings("rawtypes")
	public void configWith(File filename) throws ConfigurationException, FileNotFoundException {

		try {

			SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
			Schema schema = factory.newSchema(new StreamSource(this.getClass().getClassLoader().getResourceAsStream("dmc.xsd")));

			JAXBContext jc = JAXBContext.newInstance(it.webrules.xsd.dmc.config.v1.ObjectFactory.class);
			Unmarshaller unmarshaller = jc.createUnmarshaller();
			unmarshaller.setSchema(schema);
			Object config = ((JAXBElement) unmarshaller.unmarshal(filename)).getValue();

			logger.debug("Success parsed {}.", filename.getAbsolutePath());

			if (config instanceof StaticConfig) {

				staticConfig = (StaticConfig) config;

				if (command.equals(Command.Checkout) || command.equals(Command.Deploy)) {

					if (type == null)
						throw new ConfigurationException("Specify scm forlder name (-t).");

					if (type.equals(Type.Trunk) && name != null)
						throw new ConfigurationException("When type is Trunk don't specify name.");

					if (!type.equals(Type.Trunk) && name == null)
						throw new ConfigurationException("Specify name of tag/branch.");
				}

			} else {

				webappConfig = (WebappConfig) config;

				if (buildNumber != null && artifactFile != null)
					throw new ConfigurationException("Specify only one between -b and -a!");

				if (buildNumber != null)
					logger.info("Build number to deploy {}.", buildNumber);

				if (artifactFile != null) {
					logger.info("Artifact to deploy {}.", artifactFile);

					if (!artifactFile.exists())
						throw new ConfigurationException("Artifact file not found: " + artifactFile + ".");
				}

			}

		} catch (UnmarshalException e) {
			if (e.getLinkedException() instanceof FileNotFoundException)
				throw ((FileNotFoundException) e.getLinkedException());
			else if (e.getCause() instanceof SAXParseException)
				throw new ConfigurationException("Configuration file not well formed or not valid! " + e.getCause().getMessage());
			else
				throw new ConfigurationException(e.getMessage(), e);
		} catch (SAXException e) {
			throw new ConfigurationException("Schema file not well formed or not valid! " + e.getMessage(), e);
		} catch (Exception e) {
			throw new ConfigurationException(e.getMessage(), e);
		}

	}

	public String getBuildNumber() {
		return buildNumber;
	}

	public WebappConfig getWebappConfig() {
		return webappConfig;
	}

	public StaticConfig getStaticConfig() {
		return staticConfig;
	}

	public Build getBuildElement() {
		return webappConfig.getBuild();
	}

	public Repository getRepositoryElement() {
		return webappConfig.getRepository();
	}

	public Deploys getDeploysElement() {
		return webappConfig.getDeploys();
	}

	public GeneralConfig getGeneralConfig() {
		return webappConfig.getConfig();
	}

	public File getArtifact() {
		return artifactFile;
	}

	public Command getCommand() {
		return command;
	}

	public File getConfigFile() {
		return configFile;
	}

	public File getArtifactFile() {
		return artifactFile;
	}

	public Type getType() {
		return type;
	}

	public String getName() {
		return name;
	}

	public String getReleaseVersion() {
		return releaseVersion;
	}

	public String getDevelopmentVersion() {
		return developmentVersion;
	}

	public Boolean getSwitchin() {
		return switchin;
	}

	public File getGraphOutputFile() {
		return graphOutputFile;
	}

	public Type getSrctype() {
		return srctype;
	}

	public String getSrcname() {
		return srcname;
	}

	public Boolean getForceCheckout() {
		return forcecheckout;
	}

	public ConfigurationService setConfigFile(File configFile) {
		this.configFile = configFile;
		return this;
	}

	public ConfigurationService setCommand(Command command) {
		this.command = command;
		return this;
	}

	public ConfigurationService setBuildNumber(String buildNumber) {
		this.buildNumber = buildNumber;
		return this;
	}

	public ConfigurationService setArtifactFile(File artifactFile) {
		this.artifactFile = artifactFile;
		return this;
	}

	public ConfigurationService setType(Type type) {
		this.type = type;
		return this;
	}

	public ConfigurationService setSrctype(Type srctype) {
		this.srctype = srctype;
		return this;
	}

	public ConfigurationService setSrcname(String srcname) {
		this.srcname = srcname;
		return this;
	}

	public ConfigurationService setSwitchin(Boolean switchin) {
		this.switchin = switchin;
		return this;
	}

	public ConfigurationService setForcecheckout(Boolean forcecheckout) {
		this.forcecheckout = forcecheckout;
		return this;
	}

	public ConfigurationService setName(String name) {
		this.name = name;
		return this;
	}

	public ConfigurationService setReleaseVersion(String releaseVersion) {
		this.releaseVersion = releaseVersion;
		return this;
	}

	public ConfigurationService setDevelopmentVersion(String developmentVersion) {
		this.developmentVersion = developmentVersion;
		return this;
	}

	public ConfigurationService setGraphOutputFile(File graphOutputFile) {
		this.graphOutputFile = graphOutputFile;
		return this;
	}

}
